package com.haiheng.voiceandbook.utils;

import android.app.Application;
import android.util.Log;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class ByteUtils {

    private static final String TAG = "ByteUtils";

    public static void init(Application application, File dexFile) {
        //1、获取应用的ClassLoader对象 PathClassLoader
        ClassLoader classLoader = application.getClassLoader();
        //2、通过对象获取Class
        Class clzz = classLoader.getClass();
        //3、获取PathClassLoader父类的Class即 BaseDexClassLoader的Class
        Class fatherClass = clzz.getSuperclass();

        try {
            //4、获取BaseDexClassLoader中的 DexPathList pathList成员(私有成员)
            Field field =  fatherClass.getDeclaredField("pathList");
            //5、设置权限
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }
            //6、拿到成员属性field之后，将field转化成pathList实例
            //这样我们就通过反射拿到了BaseDexClassLoader中的pathList实例
            //这里的classLoader是PathClassLoader，PathClassLoader继承了BaseDexClassLoader
            //通过子类可以直接获取父类实例的成员变量
            Object pathList = field.get(classLoader);


            //7、调用pathList对象中makePathElements方法，将dex转换成Element[]数组

            //（1）构建方法需要传递的参数optimizedDirectory、suppressedExceptions、files
            File optimizedDirectory = application.getCacheDir();
            ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
            ArrayList<File> files = new ArrayList<File>();
            files.add(dexFile);

            //（2）找到pathList对象中的  makePathElements方法
            Method makePathElements = findMethod(pathList,"makePathElements", List.class, File.class,
                    List.class);
            //（3）执行makePathElements方法，将dex转换成Element[]数组
            Object patchElements []  = (Object[]) makePathElements.invoke(pathList, files, optimizedDirectory,
                    suppressedExceptions);

            if(patchElements==null){
                Log.e(TAG, "转换成patchElements失败");
            }
            else{
                Log.e(TAG, "转换成patchElements成功"+patchElements.length);
            }
            //4、将我们的patchElements数组和原来的数组合并，插装到第一位
            expandFieldArray(pathList,patchElements);


        } catch (Exception e) {
            Log.e(TAG, "init: "+e.getMessage() );
            e.printStackTrace();
        }


    }

    /**
     * 将两个数组合并
     * @param pathList
     * @param patchElements
     */
    private static void expandFieldArray(Object pathList, Object[] patchElements) {
        //1、获取pathList对象原来的数组 dexElements
        try {
            //2、通过反射拿到Field成员，getDeclaredField获取私有的属性
            Field field  = pathList.getClass().getDeclaredField("dexElements");
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }
            //3、将field成员转化成dexElements实例
            Object[] dexElements = (Object[]) field.get(pathList);

            //4、创建一个新的数组
            Object[] newElements = (Object[]) Array.newInstance(dexElements.getClass().getComponentType(),
                    dexElements.length + patchElements.length);

            //5、先拷贝新数组
            System.arraycopy(patchElements, 0, newElements, 0, patchElements.length);
            System.arraycopy(dexElements, 0, newElements, patchElements.length, dexElements.length);

            //6、拷贝完毕之后设置到pathList实例的dexElements中
            field.set(pathList,newElements);
            Log.e(TAG, "字节码插装成功");


        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "字节码插装失败"+e.getMessage());
        }


    }

    /**
     * 查找makePathElements方法
     * @param instance
     * @param name
     * @param parameterTypes
     * @return
     * @throws NoSuchMethodException
     */
    public static Method findMethod(Object instance, String name, Class<?>... parameterTypes)
            throws NoSuchMethodException {
        for (Class<?> clazz = instance.getClass(); clazz != null; clazz = clazz.getSuperclass()) {
            try {
                Method method = clazz.getDeclaredMethod(name, parameterTypes);

                if (!method.isAccessible()) {
                    method.setAccessible(true);
                }

                return method;
            } catch (NoSuchMethodException e) {
                // ignore and search next
            }
        }
        throw new NoSuchMethodException("Method "
                + name
                + " with parameters "
                + Arrays.asList(parameterTypes)
                + " not found in " + instance.getClass());
    }

}
